package com.hb.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.hb.commons.StringRedisService;
import com.hb.demo.entity.AdminUser;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Jwt工具类
 */
@Slf4j
public class JwtUtils {
    /**
     * 过期时间 毫秒 一小时
     */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    /**
     * 密钥
     */
    public static final String APP_SECRET = "hb_!@#$%^";
    /**
     * 登录用户Token令牌缓存KEY前缀
     */
    public static final String PREFIX_USER_TOKEN = "prefix_user_token_";

    /**
     * 请求头中token存储key值
     */
    public static final String X_ACCESS_TOKEN = "X-Access-Token";

    /**
     * 微服务读取配置文件属性
     * 服务地址
     */
    public static final String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";

    /**
     * 生成Jwt Token
     * token有效期为24小时
     * 具体参数以com.hb.home.util.JwtUtils#EXPIRE为准
     *
     * @param user
     * @return Jwt Token
     */
    public static String geneJsonWebToken(AdminUser user) {
        if (user == null) {
            return null;
        }
        // 当前系统时间
        long currentTimestamp = DateUtil.current();

        String timestampSign = encode(currentTimestamp);

        return JWT.create()
                .withExpiresAt(DateUtil.date(System.currentTimeMillis() + EXPIRE))  //设置过期时间
                .withAudience(user.getUname()) //设置接受方信息，一般是登录用户
                .withClaim("id", user.getId())
                .withClaim("username", user.getUname())
                .withClaim("password", user.getPword()) // 这里应该是密文
                .withClaim("timestampSign", timestampSign)
                .sign(Algorithm.HMAC256(APP_SECRET));  //使用HMAC算法，111111作为密钥加密
    }

    /**
     * 校验token
     *
     * @param token
     */
    public static boolean checkToken(String token) {
        return parseToken(token) != null;
    }

    /**
     * 私有化方法
     * 校验token
     *
     * @param token
     */
    private static Map<String, Claim> parseToken(String token) {
        if (StrUtil.isEmpty(token)) {
            return null;
        }
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(APP_SECRET)).build();
        try {
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return decodedJWT.getClaims();
        } catch (Exception e) {
            // e.printStackTrace();
            log.warn("不是一个合法的token值");
        }
        return null;
    }

    /**
     * 从token中获取指定值
     *
     * @param token
     * @param clazz
     */
    public static <T> T get(String token, String key, Class<T> clazz) {
        if (StrUtil.isEmpty(token)) {
            return null;
        }
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        if (checkToken(token)) {
            Map<String, Claim> claims = parseToken(token);
            if (claims != null) {
                return claims.get(key).as(clazz);
            }
        }
        return null;
    }

    /**
     * 从token中获取指定值
     *
     * @param token
     */
    public static Long get(String token, String key) {
        return get(token, key, Long.class);
    }

    /**
     * 从token中获取userId
     *
     * @param token
     * @return String
     */
    public static Long getUserId(String token) {
        return get(token, "id");
    }

    /**
     * 从token中获取username
     *
     * @param token
     * @return String
     */
    public static String getUsername(String token) {
        return get(token, "username", String.class);
    }

    /**
     * 从token中获取password
     *
     * @param token
     * @return String
     */
    public static String getPassword(String token) {
        return get(token, "password", String.class);
    }

    /**
     * 校验token是否正确
     *
     * @param token redis中保存的token
     * @return 是否正确
     */
    public static boolean verify(String token) {
        // 校验时间戳
        String timestampSign = get(token, "timestampSign", String.class);

        if (StrUtil.isEmpty(timestampSign)) { // 没有此字段直接返回false
            return false;
        }

        // 解密字符串
        long timestamp = decode(timestampSign);
        if (timestamp > 0) {
            // 计算token中的时间戳与当前时间戳相差的小时数
            Date now = DateUtil.date();
            Date validate = DateUtil.date(timestamp);
            long diff = DateUtil.between(now, validate, DateUnit.HOUR);
            boolean diffResult = diff < 24;

            // 24小时之前的token统一为失效
            if (diffResult) {
                return true;
            }
            log.error("token已经超过24小时, 系统判定为失效");
            return false;
        }
        log.error("token中[timestampSign]验证无效: [timestampSign: {}]", timestampSign);
        return false;
    }

    /**
     * 加密信息存放位置
     */
    private static final int REDIS_DB_NUM = 8;

    /**
     * 加密一个时间戳
     *
     * @param timestamp
     * @return
     */
    private static String encode(long timestamp) {
        // 生成随机加密次数(加密次数不要太大, 否则密文的长度会呈几何级别增长, 造成服务器负担)
        int randomCount = RandomUtil.randomInt(1, 5);

        String timestampSign = String.valueOf(timestamp); // 加密后的字符串
        for (int i = 0; i < randomCount; i++) {
            timestampSign = Base64.encode(timestampSign);
        }
        // 随机加密次数存入redis(有效期24小时)
        StringRedisService redisService = SpringUtil.getBean(StringRedisService.class);
        redisService.setDataBase(REDIS_DB_NUM);
        redisService.set(timestampSign, String.valueOf(randomCount), 24, TimeUnit.HOURS);
        // 恢复redis默认的存储DB索引
        redisService.setDataBase(0);
        return timestampSign;
    }

    /**
     * 解密时间戳密文
     *
     * @param secret token中时间戳的密文
     * @return
     */
    private static long decode(String secret) {
        if (StrUtil.isEmpty(secret)) { // 滚!
            return -1L;
        }

        // 取出相对应的随机加密次数
        StringRedisService redisService = SpringUtil.getBean(StringRedisService.class);
        redisService.setDataBase(REDIS_DB_NUM);
        String randomCountStr = redisService.get(secret);

        if (StrUtil.isEmpty(randomCountStr)) {
            return -1L;
        }

        // 字符串转long
        long randomCount = NumberUtil.parseLong(randomCountStr);

        // 声明一个临时变量
        String temp = secret;
        for (int i = 0; i < randomCount; i++) {
            temp = Base64.decodeStr(temp);
        }
        // 恢复redis默认的存储DB索引
        redisService.setDataBase(0);
        return NumberUtil.parseLong(temp);
    }

}
